Intention = class("Intention")

require("IntentionGoalConstraints")
require("IntentionValuesExtenders")

---

function Intention:initialize(sentenceDescriptor)

	self.sentence = Sentence:new(sentenceDescriptor)
	self.realized = false
	self.matchingEffectors = {}
	
	-- format of constraintTrackingTable :
	-- [goalVariableName] [propertyName] [entity] [potentialValue] = probability
		
	self.constraintTrackingTable = {}
	
	
	-- find matching goal effects
	
	for mindName, mind in pairs(gField.entityTypes.Mind)
	do
		local behavior = mind.behavior
		
		if 
			behavior and
			behavior.effectors
		then
			for effectorIndex, effector in ipairs(behavior.effectors)
			do
				if effector:isEffectMatching(self.sentence)
				then
					self.matchingEffectors[#self.matchingEffectors + 1] = {behavior = behavior, effector = effector}
				end
			end
		end
	end
end

---

function Intention:isImplyingEntity(entity)

	return self.sentence:getDirectObject() == entity
end

---

function Intention:isRealized()

	return self.realized
end


--- see:
---		Retro-causality in Potential-Goals exemple.graphml
---		Constraints in Potential-Goals exemple.graphml

function Intention:tryToRealize()

	gLogger:enterGroup("Try to realize intention " .. tostring(self.sentence))
	
	self.realized = false

	
	-- try all matching effectors

	for i, effectorDescriptor in ipairs(self.matchingEffectors)
	do
	
		-- make a plan thanks to regression planning
		
		self.plan = {}
		
		gLogger:enterGroup("Try to do regression planning from " .. effectorDescriptor.effector.name)
		
		if effectorDescriptor.effector:tryToRegress(self)
		then
			gLogger:leaveGroup()
			gLogger:enterGroup("Check intersection constraints")
			
			
			-- regression planning worked, so check intersection constraints
			-- see "Constraints in Potential-Goals exemple.graphml" for more information on
			-- intersection constraints
		
			self.constraintTrackingTable = {}
			
			local enteredExtentionLogGroup = false
			
			for i, goal in ipairs(self.plan)
			do
				local constraint = goal.constraint
				local set1Entities
				
				if goal.constraint
				then
					
					-- log management
					
					if enteredExtentionLogGroup
					then
						gLogger:leaveGroup()
						enteredExtentionLogGroup = false
					end
					
					assert(goal.constraint.set1EntitiesType, "Goal constraint set1 must always be entities")
					gLogger:enterGroup("Consider constraints of goal " .. goal.name)

					
					-- get the set 1 in constraintTrackingTable

					set1Entities = self:getSubConstraintTrackingTable(
						goal.constraint.set1EntitiesType, 
						goal.constraint.set1EntitiesNames,
						goal
					)
					
					
					-- call the constraint solving function corresponding to the constraint type
					-- all the solving functions are implemented in IntentionGoalConstraints.lua
					
					self[constraint.type](self, set1Entities, goal)
						
					
					-- log management
					
					gLogger:leaveGroup()
					gLogger:enterGroup("Extend values in constraint tracking table of one beat of backward time")
					enteredExtentionLogGroup = true
				end
				
				
				-- extend constraint values to represent the next goal that will be back in time of one beat
				
				
				for goalVariableName, properties in pairs(self.constraintTrackingTable)
				do
					for propertyName, entities in pairs(properties)
					do
						gLogger:enterGroup("Extend potential " .. propertyName)
						
						
						-- find the extender function in regard to the Properties of the constraint
						-- all the extender functions are implemented in IntentionValuesExtenders.lua

						local extendingFunction = self["extendPotential" .. propertyName]
						
						if extendingFunction
						then
							for entity, values in pairs(entities)
							do
								gLogger:log("Extend values of entity " .. entity.type .. " " .. entity.name)
								
								entities[entity] = extendingFunction(self, values)
							end
						end
						
						gLogger:leaveGroup()
					end
				end
			end
			
			
			-- log management
			
			if enteredExtentionLogGroup
			then
				gLogger:leaveGroup()
				enteredExtentionLogGroup = false
			end
		
		
			-- test if at least one value in at least one entity remains in the constraints intersection
			-- array of goal variales and properties
			
			gLogger:enterGroup("Check if at least one value in at least one entity remains")
			
			local found = false
			
			for goalVariableName, properties in pairs(self.constraintTrackingTable)
			do
				gLogger:enterGroup("Check goal variable " .. goalVariableName)
				
				for propertyName, entities in pairs(properties)
				do
					gLogger:enterGroup("Check property " .. propertyName)
					
					found = false
					
					for entity, values in pairs(entities)
					do
						if next(values) 
						then
							gLogger:log("At least one value found for entity " .. entity.type .. " " .. entity.name)
							
							found = true
							break
						else
							gLogger:log("No value found for entity " .. entity.type .. " " .. entity.name)
						end
					end
					
					gLogger:leaveGroup()
				
					if not found
					then
					
						-- if at least one value is not remmaining in at least one entity, we stop in failure
						
						gLogger:log("Failure : at least one value is not remmaining in at least one entity")
						break
					end
				end
				
				gLogger:leaveGroup()
				
				if not found
				then
				
					-- we propagate the failure to exit this loop
					
					break
				end
			end
			
			gLogger:leaveGroup()
		
		
			-- if at least one entity with one value is remaining in the goal variable and 
			-- property of the intersection, we consider the constraint passed sucessfully
			
			self.realized = found
			
			gLogger:leaveGroup()
		else
			gLogger:leaveGroup()
		end
		
		if self.realized
		then
		
			-- the current matching effector passed regression planning and constraints, so we don't consider other matching effectors
			
			gLogger:log("Success : effector " .. effectorDescriptor.effector.name .. " passed regression planning and constraints")
			break
		end
	end
	
	gLogger:leaveGroup()
end

---

function Intention:getSubConstraintTrackingTable
(
	setEntitiesTypeDescriptor, 
	setEntitiesNamesDescriptor, 
	goal
) 																						
	local entitiesTypeVariableName, entitiesType = goal:getVariableNameAndValue(setEntitiesTypeDescriptor)
	local variableName, entitiesNames = goal:getVariableNameAndValue(setEntitiesNamesDescriptor)
	
	gLogger:enterGroup("Get sub constraint tracking table of entities for " .. setEntitiesNamesDescriptor)
	
	
	-- create an entry for the variable in constraintTrackingTable table
	
	if not self.constraintTrackingTable[variableName]
	then
		gLogger:log("Create an entry for the variable " .. variableName)
		self.constraintTrackingTable[variableName] = {}
	end
	
	
	-- create an entry for the property in constraintTrackingTable table
	
	if not self.constraintTrackingTable[variableName][goal.constraint.property]
	then
		gLogger:log("Create an entry for the property " .. goal.constraint.property)
		self.constraintTrackingTable[variableName][goal.constraint.property] = {}
	end
	
	
	-- select the set to return as the sub table of constraintTrackingTable
	-- for the given goal variable name and constraint property
	
	local subConstraintTrackingTable = 
		self.constraintTrackingTable[variableName][goal.constraint.property]
	
	
	-- if not yet done, copy in constraintTrackingTable the content of the property 
	-- of all entities in the given set goal variable
	
	if not next(subConstraintTrackingTable)
	then
		gLogger:enterGroup("First encounter with the variable " .. setEntitiesNamesDescriptor .. ", so initialize it the constraint tracking table")
		
		if type(entitiesNames) == "table"
		then
			
			-- constraint set is a table
			
			for i, entityName in pairs(entitiesNames)
			do
				gLogger:enterGroup("Copy values from entity " .. entitiesType .. " " .. entityName)
				
				local entity = gField.entityTypes[entitiesType][entityName]
				
				subConstraintTrackingTable[entity] = 
					self:getPropertyValuesCopy(entity, goal.constraint.property)
					
				gLogger:leaveGroup()
			end
		else
		
			-- constraint set is an entity, copy it as the unique entry in the constraint table
			
			gLogger:enterGroup("Copy values from entity " .. entitiesType .. " " .. entitiesNames)
			
			local entity = gField.entityTypes[entitiesType][entitiesNames]
			
			subConstraintTrackingTable[entity] = 
				self:getPropertyValuesCopy(entity, goal.constraint.property)
				
			gLogger:leaveGroup()
		end
		
		gLogger:leaveGroup()
	end
	
	gLogger:leaveGroup()
	
	return subConstraintTrackingTable
end

---

function Intention:getPropertyValuesCopy(entity, property)

	local values = {}				
	
	for value, probability in pairs(entity.properties[property])
	do
		if value.isEntity
		then
			gLogger:log("Copy property " .. property .. " with value " .. value.type .. " " .. value.name .. " with probability " .. tostring(probability))
		else
			gLogger:log("Copy property " .. property .. " with value " .. tostring(value) .. " with probability " .. tostring(probability))
		end
		
		values[value] = probability
	end
	
	return values
end
